// Captain Proto -- Protobuf Object-capability RPC
// Copyright (c) 2009 Kenton Varda, Google Inc., and contributors
// http://capnproto.googlecode.com
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
//     * Neither the name of the Captain Proto project nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

package capnproto;

import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;

import capnproto.TestProto.CallbackService;
import capnproto.TestProto.CallbackRequest;
import capnproto.TestProto.CallbackResponse;
import capnproto.TestProto.CreateCallbackRequest;
import capnproto.TestProto.CreateCallbackResponse;
import capnproto.TestProto.DoCallbackRequest;
import capnproto.TestProto.DoCallbackResponse;
import capnproto.TestProto.HelloRequest;
import capnproto.TestProto.HelloResponse;
import capnproto.TestProto.TestService;

final class Test {
  private static boolean failed = false;

  private static class CallbackServiceImpl extends CallbackService {
    private final String expectedText;
    private final int replyValue;

    CallbackServiceImpl(String expectedText, int replyValue) {
      this.expectedText = expectedText;
      this.replyValue = replyValue;
    }

    @Override
    public CallbackResponse callback(
        RpcContext context, CallbackRequest request) throws ServiceException {
      System.out.println("  In callback()...");
      if (!request.getText().equals(expectedText)) {
        failed = true;
        System.err.println(
            "ERROR: Callback received wrong message: " + request);
      }
      return CallbackResponse.newBuilder().setValue(replyValue).build();
    }
  }

  private static class ErrorCallbackServiceImpl extends CallbackService {
    private final String expectedText;
    private final String errorMessage;

    ErrorCallbackServiceImpl(String expectedText, String errorMessage) {
      this.expectedText = expectedText;
      this.errorMessage = errorMessage;
    }

    @Override
    public CallbackResponse callback(
        RpcContext context, CallbackRequest request) throws ServiceException {
      System.out.println("  In error callback()...");
      if (!request.getText().equals(expectedText)) {
        failed = true;
        System.err.println(
            "ERROR: Callback received wrong message: " + request);
      }
      throw new ServiceException(errorMessage);
    }
  }

  private static class TestServiceImpl extends TestService {
    @Override
    public HelloResponse hello(RpcContext context, HelloRequest request)
        throws ServiceException {
      System.out.println("  In hello()...");
      return HelloResponse.newBuilder()
          .setMessage("count: " + request.getCount()).build();
    }

    @Override
    public DoCallbackResponse doCallback(RpcContext context,
        DoCallbackRequest request) throws ServiceException {
      System.out.println("  In doCallback()...");
      CallbackService callback =
        request.getCallbackService(context.getRequestServiceReceiver());
      try {
        CallbackResponse subResponse = callback.callback(new RpcContext(),
            CallbackRequest.newBuilder().setText(request.getText()).build());
        return DoCallbackResponse.newBuilder()
            .setValue(subResponse.getValue()).build();
      } catch (ServiceException e) {
        return DoCallbackResponse.newBuilder().setError(e.getMessage()).build();
      }
    }

    @Override
    public CreateCallbackResponse createCallback(RpcContext context,
        CreateCallbackRequest request) throws ServiceException {
      System.out.println("  In createCallback()...");
      return CreateCallbackResponse.newBuilder()
          .setCallbackService(
            new CallbackServiceImpl(
              request.getExpectedText(), request.getReplyValue()),
            context.getResponseServiceSender())
          .build();
    }
  }

  private static class ServerThread implements Runnable {
    private ServerSocket listener;

    ServerThread(ServerSocket listener) {
      this.listener = listener;
    }

    @Override
    public void run() {
      try {
        Socket socket = listener.accept();
        listener.close();
        Connection connection = new Connection(socket);
        connection.setDefaultService(new TestServiceImpl().toChannel());
        connection.runEventLoop();
        socket.close();
      } catch (Throwable e) {
        failed = true;
        e.printStackTrace();
      }
    }
  }

  private static class ClientThread implements Runnable {
    private Connection connection;

    ClientThread(Connection connection) {
      this.connection = connection;
    }

    @Override
    public void run() {
      try {
        connection.runEventLoop();
      } catch (Throwable e) {
        failed = true;
        e.printStackTrace();
      }
    }
  }

  public static void main(String[] args) throws Exception {
    System.out.println("Setting up...");

    InetAddress loopback = InetAddress.getByName(null);

    int port = 51309;
    ServerSocket listener =
        new ServerSocket(port, -1, loopback);
    Thread serverThread = new Thread(new ServerThread(listener));
    serverThread.setName("serverThread");
    serverThread.start();

    Socket socket = new Socket(loopback, port);
    Connection connection = new Connection(socket);
    TestService testService =
        TestService.fromChannel(connection.getDefaultService());
    Thread clientThread = new Thread(new ClientThread(connection));
    clientThread.setName("clientThread");
    clientThread.start();

    doHelloTest(testService);
    doCallbackTest(testService);
    doErrorCallbackTest(testService);
    doCreateCallbackTest(testService);

    System.out.println("Shutting down...");
    socket.shutdownOutput();

    serverThread.join();
    clientThread.join();

    if (failed) {
      System.err.println("FAILED");
      System.exit(1);
    } else {
      System.out.println("PASSED");
    }
  }

  private static void doHelloTest(TestService testService)
      throws ServiceException {
    System.out.println("Calling hello()...");
    HelloResponse response = testService.hello(new RpcContext(),
        HelloRequest.newBuilder().setCount(123).build());

    if (!response.getMessage().equals("count: 123")) {
      failed = true;
      System.err.println("Bad HelloResponse: " + response);
    }
  }

  private static void doCallbackTest(TestService testService)
      throws ServiceException {
    System.out.println("Calling doCallback()...");

    CallbackServiceImpl callback = new CallbackServiceImpl("foo", 123);

    RpcContext controller = new RpcContext();

    DoCallbackResponse response =
      testService.doCallback(controller,
        DoCallbackRequest.newBuilder()
          .setCallbackService(callback, controller.getRequestServiceSender())
          .setText("foo")
          .build());

    if (response.getValue() != 123 || response.hasError()) {
      failed = true;
      System.err.println("Bad DoCallbackResponse: " + response);
    }
  }

  private static void doErrorCallbackTest(TestService testService)
      throws ServiceException {
    System.out.println("Calling doCallback() with an erroring callback...");

    ErrorCallbackServiceImpl callback =
      new ErrorCallbackServiceImpl("foo", "corge");

    RpcContext controller = new RpcContext();

    DoCallbackResponse response =
      testService.doCallback(controller,
        DoCallbackRequest.newBuilder()
          .setCallbackService(callback, controller.getRequestServiceSender())
          .setText("foo")
          .build());

    if (!response.getError().equals("corge") || response.hasValue()) {
      failed = true;
      System.err.println("Bad DoCallbackResponse: " + response);
    }
  }

  private static void doCreateCallbackTest(TestService testService)
      throws ServiceException {
    System.out.println("Calling createCallback()...");

    RpcContext controller = new RpcContext();

    CreateCallbackResponse response =
      testService.createCallback(controller,
        CreateCallbackRequest.newBuilder()
          .setExpectedText("bar")
          .setReplyValue(456)
          .build());

    CallbackService callback =
      response.getCallbackService(controller.getResponseServiceReceiver());

    CallbackResponse callbackResponse =
      callback.callback(new RpcContext(),
          CallbackRequest.newBuilder().setText("bar").build());

    if (callbackResponse.getValue() != 456) {
      failed = true;
      System.err.println("Bad CallbackResponse: " + callbackResponse);
    }
  }
}
